use std::{collections::HashMap, rc::Rc, sync::Arc, task::Poll};

use crate::services::session_service::SessionService;
use actix_http::header::HeaderValue;
use actix_session::{Session, SessionStatus};
use actix_web::{
    cookie::{Cookie, CookieJar, Key, SameSite},
    dev::{Service, ServiceRequest, ServiceResponse, Transform},
    http::header,
};
use futures::future::LocalBoxFuture;
use rand::{distributions::Alphanumeric, rngs::OsRng, Rng};
use serde::{Deserialize, Serialize};
use time::{Duration, OffsetDateTime};

pub struct CacheSession(Rc<Inner>);

impl CacheSession {
    pub fn new<S: Into<String>>(
        ns: S,
        key: &[u8],
        db: sled::Db,
        session_svr: SessionService,
    ) -> CacheSession {
        let ns = ns.into();
        CacheSession(Rc::new(Inner {
            db,
            key: Key::derive_from(key),
            ns,
            name: "cache-session".to_owned(),
            path: "/".to_owned(),
            domain: None,
            secure: false,
            max_age: Some(Duration::days(7)),
            same_site: None,
            http_only: true,
            session_svr: Arc::new(session_svr),
        }))
    }
    pub fn cookie_name(mut self, name: &str) -> Self {
        Rc::get_mut(&mut self.0).unwrap().name = name.to_owned();
        self
    }
    pub fn cookie_path(mut self, path: &str) -> Self {
        Rc::get_mut(&mut self.0).unwrap().path = path.to_owned();
        self
    }
    pub fn cookie_domain(mut self, domain: &str) -> Self {
        Rc::get_mut(&mut self.0).unwrap().domain = Some(domain.to_owned());
        self
    }
    pub fn cookie_secure(mut self, secure: bool) -> Self {
        Rc::get_mut(&mut self.0).unwrap().secure = secure;
        self
    }
    pub fn cookie_max_age(mut self, max_age: impl Into<Option<Duration>>) -> Self {
        Rc::get_mut(&mut self.0).unwrap().max_age = max_age.into();
        self
    }
    pub fn cookie_http_only(mut self, http_only: bool) -> Self {
        Rc::get_mut(&mut self.0).unwrap().http_only = http_only;
        self
    }
    pub fn cookie_same_site(mut self, same_site: SameSite) -> Self {
        Rc::get_mut(&mut self.0).unwrap().same_site = Some(same_site);
        self
    }
    pub fn cache_namespace(mut self, namespace: &str) -> Self {
        Rc::get_mut(&mut self.0).unwrap().ns = namespace.to_owned();
        self
    }
}

impl<S, B> Transform<S, ServiceRequest> for CacheSession
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = actix_web::Error> + 'static,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    type Error = S::Error;
    type Transform = CacheSessionMiddleware<S>;
    type InitError = ();
    type Future = LocalBoxFuture<'static, Result<Self::Transform, Self::InitError>>;
    fn new_transform(&self, service: S) -> Self::Future {
        let inner = self.0.clone();
        Box::pin(async {
            Ok(CacheSessionMiddleware {
                service: Rc::new(service),
                inner,
            })
        })
    }
}

pub struct CacheSessionMiddleware<S: 'static> {
    service: Rc<S>,
    inner: Rc<Inner>,
}

impl<S, B> Service<ServiceRequest> for CacheSessionMiddleware<S>
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = actix_web::Error> + 'static,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    type Error = S::Error;
    type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
    fn poll_ready(
        &self,
        _: &mut core::task::Context<'_>,
    ) -> std::task::Poll<Result<(), Self::Error>> {
        Poll::Ready(Ok(()))
    }
    fn call(&self, mut req: ServiceRequest) -> Self::Future {
        let svr = Rc::clone(&self.service);
        let inner = Rc::clone(&self.inner);

        Box::pin(async move {
            let state = inner.load(&req)?;
            let cache_key = if let Some((state, value)) = state {
                Session::set_session(&mut req, state);
                Some(value)
            } else {
                None
            };

            let mut res = svr.call(req).await?;

            match Session::get_changes(&mut res) {
                (SessionStatus::Unchanged, _state) => {
                    // if value.is_none() {
                    //     inner.update(res, state, value)
                    // } else {
                    //     Ok(res)
                    // }
                    Ok(res)
                }
                (SessionStatus::Changed, state) => inner.update(res, state, cache_key).await,
                (SessionStatus::Purged, _) => {
                    if let Some(val) = cache_key {
                        inner.clear_cache(val).await?;
                        inner.remove_cookie(&mut res)?;
                        Ok(res)
                    } else {
                        Err(actix_web::error::ErrorInternalServerError("unexpected"))
                    }
                }
                (SessionStatus::Renewed, state) => {
                    if let Some(key) = cache_key {
                        inner.clear_cache(key).await?;
                    }
                    inner.update(res, state, None).await
                }
            }
        })
    }
}

pub struct Inner {
    db: sled::Db,
    ns: String,
    name: String,
    key: Key,
    path: String,
    domain: Option<String>,
    secure: bool,
    max_age: Option<time::Duration>,
    same_site: Option<SameSite>,
    http_only: bool,
    session_svr: Arc<SessionService>,
}
type LoadRes = Result<Option<(HashMap<String, String>, String)>, actix_web::Error>;
impl Inner {
    pub fn load(&self, req: &ServiceRequest) -> LoadRes {
        let (value, cache_key) = {
            let cookies = if let Ok(cookies) = req.cookies() {
                cookies
            } else {
                return Ok(None);
            };
            if let Some(cookie) = cookies.iter().find(|&cookie| cookie.name() == self.name) {
                let mut jar = CookieJar::new();
                jar.add_original(cookie.clone());
                if let Some(cookie) = jar.signed(&self.key).get(&self.name) {
                    let value = cookie.value().to_owned();
                    let cache_key = cookie.value().to_owned();
                    (value, cache_key)
                } else {
                    return Ok(None);
                }
            } else {
                return Ok(None);
            }
        };
        let tree = self
            .db
            .open_tree(&self.ns)
            .map_err(actix_web::error::ErrorInternalServerError)?;
        match tree
            .get(cache_key)
            .map_err(actix_web::error::ErrorInternalServerError)?
        {
            Some(cache_vec) => {
                if let Ok(val) = serde_json::from_slice(cache_vec.to_vec().as_slice()) {
                    let cache: CacheData = val;
                    match cache {
                        CacheData::Persistent(val) => {
                            return Ok(Some((val, value)));
                        }
                        CacheData::Expirable(val) => {
                            let now = chrono::Utc::now();
                            if val.expired_time < now {
                                return Ok(None);
                            }
                            return Ok(Some((val.value, value)));
                        }
                    }
                }
            }
            None => return Ok(None),
        };
        Ok(None)
    }
    pub async fn update<B>(
        &self,
        mut res: ServiceResponse<B>,
        state: impl Iterator<Item = (String, String)>,
        value: Option<String>,
    ) -> Result<ServiceResponse<B>, actix_web::Error> {
        let (jar, value_vec, id_string) = if let Some(value) = value {
            (None, value.as_bytes().to_vec(), value)
        } else {
            let value_vec = std::iter::repeat(())
                .map(|()| OsRng.sample(Alphanumeric))
                .take(32)
                .collect::<Vec<_>>();
            let value = String::from_utf8(value_vec.clone()).unwrap_or_default();
            let mut cookie = Cookie::new(self.name.clone(), value.clone());
            cookie.set_path(self.path.clone());
            cookie.set_secure(self.secure);
            cookie.set_http_only(self.http_only);
            if let Some(ref domain) = self.domain {
                cookie.set_domain(domain.clone());
            }
            if let Some(max_age) = self.max_age {
                cookie.set_max_age(max_age);
            }

            if let Some(same_site) = self.same_site {
                cookie.set_same_site(same_site);
            }
            let mut jar = CookieJar::new();
            jar.signed_mut(&self.key).add(cookie);
            (Some(jar), value_vec, value)
        };

        let state: HashMap<_, _> = state.collect();
        let (cache_state, exp) = if let Some(ref max_age) = self.max_age {
            let ep = chrono::Utc::now()
                + chrono::Duration::milliseconds(max_age.whole_milliseconds() as i64);
            (
                CacheData::Expirable(ExpirableCacheData {
                    value: state,
                    expired_time: ep,
                }),
                Some(ep),
            )
        } else {
            (CacheData::Persistent(state), None)
        };
        let body = match serde_json::to_string(&cache_state) {
            Err(err) => return Err(err.into()),
            Ok(body) => body,
        };
        let tree = self
            .db
            .open_tree(&self.ns)
            .map_err(actix_web::error::ErrorInternalServerError)?;
        tree.insert(value_vec.as_slice(), body.as_bytes())
            .map_err(actix_web::error::ErrorInternalServerError)?;
        if let (CacheData::Expirable(data), Some(exp)) = (cache_state, exp) {
            // tokio::spawn(async move {
            //     svr.upsert_sessin(id_string, exp, data.value).await.ok();
            // });
            match self
                .session_svr
                .upsert_sessin(id_string, exp, data.value)
                .await
            {
                Ok(_) => {}
                Err(e) => {
                    log::error!("更新session出错: {}", e);
                }
            };
        }
        if let Some(jar) = jar {
            for cookie in jar.delta() {
                let val = HeaderValue::from_str(&cookie.to_string())?;
                res.headers_mut().append(header::SET_COOKIE, val);
            }
        }
        Ok(res)
    }
    pub async fn clear_cache(&self, key: String) -> Result<(), actix_web::Error> {
        let cache_key = key;
        let tree = self
            .db
            .open_tree(&self.ns)
            .map_err(actix_web::error::ErrorInternalServerError)?;
        tree.remove(cache_key.as_bytes())
            .map_err(actix_web::error::ErrorInternalServerError)?;
        match self.session_svr.delete_session(cache_key).await {
            Ok(_) => {}
            Err(e) => {
                log::error!("清除session出错: {}", e)
            }
        };
        Ok(())
    }
    pub fn remove_cookie<B>(&self, res: &mut ServiceResponse<B>) -> Result<(), actix_web::Error> {
        let mut cookie = Cookie::named(self.name.clone());
        cookie.set_value("");
        cookie.set_max_age(Duration::ZERO);
        cookie.set_expires(OffsetDateTime::now_utc() - Duration::days(365));
        let val = HeaderValue::from_str(&cookie.to_string())
            .map_err(actix_web::error::ErrorInternalServerError)?;
        res.headers_mut().append(header::SET_COOKIE, val);

        Ok(())
    }
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExpirableCacheData {
    pub value: HashMap<String, String>,
    pub expired_time: chrono::DateTime<chrono::Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "t")]
pub enum CacheData {
    Expirable(ExpirableCacheData),
    Persistent(HashMap<String, String>),
}
